package com.dandandog.framework.faces.controller;

import cn.hutool.core.util.ObjectUtil;
import com.dandandog.framework.common.utils.LocaleUtil;
import com.dandandog.framework.common.utils.MessageUtil;
import com.dandandog.framework.faces.scope.*;
import com.dandandog.framework.faces.utils.FacesMessageUtils;
import org.primefaces.event.SelectEvent;
import org.primefaces.util.LocaleUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.jsf.FacesContextUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.annotation.Resource;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;

/**
 * @author JohnnyLiu
 */
@Component
public class FacesController {
    @Resource
    protected FlashScope flashScope;

    @Resource
    protected ViewScope viewScope;

    @Resource
    protected RequestScope requestScope;

    @Resource
    protected SessionScope sessionScope;

    @Resource
    protected ApplicationScope applicationScope;

    @Resource
    protected LocaleResolver localeResolver;


    public static FacesController getCurrentInstance() {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        String path = facesContext.getExternalContext().getRequestServletPath();
        ApplicationContext applicationContext = FacesContextUtils.getRequiredWebApplicationContext(facesContext);
        try {
            return applicationContext.getBean(path, FacesController.class);
        } catch (NoSuchBeanDefinitionException ignored) {
        }
        return null;
    }

    public void onEntry() {
    }

    public void putFlashScope(String name, Object obj) {
        this.flashScope.put(name, obj);
    }

    public <T> T getFlashScope(String name) {
        return (T) this.flashScope.get(name);
    }

    public void putViewScope(String name, Object obj) {
        this.viewScope.put(name, obj);
    }

    public <T> T getViewScope(String name) {
        return (T) this.viewScope.get(name);
    }

    public void putSessionScope(String name, Object obj) {
        this.sessionScope.put(name, obj);
    }

    public <T> T getSessionScope(String name) {
        return (T) this.sessionScope.get(name);
    }

    public void putRequestScope(String name, Object object) {
        this.requestScope.put(name, object);
    }

    public <T> T getRequestScope(String name) {
        return (T) this.requestScope.get(name);
    }

    public void putApplicationScope(String name, Object object) {
        this.applicationScope.put(name, object);
    }

    public <T> T getApplicationScope(String name) {
        return (T) this.applicationScope.get(name);
    }


    public HttpServletRequest getRequest() {
        return (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
    }

    public HttpServletResponse getResponse() {
        return (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
    }

    public HttpSession getSession() {
        return getRequest().getSession();
    }

    public void redirectInternal(String url) {
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        redirectExternal(externalContext.getRequestContextPath() + url);
    }

    public void redirectExternal(String url) {
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        try {
            externalContext.redirect(url);
        } catch (IOException e) {
            throw new FacesException(e);
        }
    }

    public String getRequestParameter(String key) {
        Map<String, String> paramValues = FacesContext.getCurrentInstance().getExternalContext()
                .getRequestParameterMap();
        return paramValues.get(key);
    }

    protected Optional<String> getOptParams(String key) {
        String value = ObjectUtil.defaultIfNull(getViewScope(key), getRequestParameter(key));
        return Optional.ofNullable(value);
    }


    public void close(UIComponent component) {
        resetForm(component);
    }

    public void cancel(UIComponent component) {
        resetForm(component);
    }

    public void resetForm(UIComponent component) {
        UIForm form = getNestingForm(component);
        if (form != null) {
            VisitContext visitContext = VisitContext.createVisitContext(FacesContext.getCurrentInstance());
            form.visitTree(visitContext, (VisitContext context, UIComponent target) -> {
                if ((target instanceof EditableValueHolder)) {
                    EditableValueHolder holder = (EditableValueHolder) target;
                    holder.resetValue();
                }
                return VisitResult.ACCEPT;
            });
        }
    }

    public UIForm getNestingForm(UIComponent component) {
        UIComponent parent = component.getParent();
        while (parent != null) {
            if ((parent instanceof UIForm)) {
                return (UIForm) parent;
            }
            parent = parent.getParent();
        }
        return null;
    }

    public void exportPostProcessor(Object document) {

    }

    public void exportPreProcessor(Object document) {

    }

    public void onChangeLocale(SelectEvent<String> selectEvent) {
        String locale = selectEvent.getObject();
        if (localeResolver != null) {
            localeResolver.setLocale(this.getRequest(), this.getResponse(), LocaleUtils.toLocale(locale));
        }
        this.getResponse().setLocale(LocaleUtils.toLocale(locale));
    }

    protected void addMessages(FacesMessage.Severity severity, String key, String clientId, Object... args) {
        String detail = MessageUtil.getMessageSource(key, LocaleUtil.getCurrLocale(), args);
        String summary = MessageUtil.getMessageSource("framework.tips", LocaleUtil.getCurrLocale(), args);
        FacesMessageUtils.show(clientId, severity, summary, detail);
    }

    protected void infoMessages(String key, String clientId, Object... args) {
        this.addMessages(FacesMessage.SEVERITY_INFO, key, clientId, args);
    }

    protected void warnMessages(String key, String clientId, Object... args) {
        this.addMessages(FacesMessage.SEVERITY_WARN, key, clientId, args);
    }

    protected void errorMessages(String key, String clientId, Object... args) {
        this.addMessages(FacesMessage.SEVERITY_ERROR, key, clientId, args);
    }

    protected void fatalMessages(String key, String clientId, Object... args) {
        this.addMessages(FacesMessage.SEVERITY_FATAL, key, clientId, args);
    }


}
